#pragma once
#include <string>
#include <iostream>
#include <filesystem>
#include "Mutex.hpp"
#include <fstream>
#include <memory>
#include <unistd.h>
#include <time.h>
#include <sstream>

namespace LogModule
{
    const std::string defaultlogpath = "./Log/";
    const std::string defaultlogname = "log.txt";
    using namespace LockModule;
    // 结构: 1. 日志内容 2. 策略落盘(屏幕，文件)
    class Logstrategy
    {
    public:
        virtual ~Logstrategy() = default;
        // 1. 提供接口，让派生类去实现不同的策略
        virtual void SysChontrolLog(const std::string &message) = 0;
    };
    // 策略落盘(屏幕，文件)
    class ConsoleLogStrategy : public Logstrategy
    {
    public:
        ConsoleLogStrategy() {}
        virtual void SysChontrolLog(const std::string &message)
        {
            // 直接向显示器中输出，但是显示器也属于临界资源，需要进行保护
            //  std::cout << message << std::endl;
            LockGuard lock(_mutex);
            std::cout << message << std::endl;
        }
        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };
    class FileLogStrategy : public Logstrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(defaultlogpath), _logname(defaultlogname)
        {
            LockGuard lock(_mutex);
            // 如果文件夹存在就直接返回就可以了
            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            // 如果不存在就创建
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cout << e.what() << std::endl;
                return;
            }
        }
        virtual void SysChontrolLog(const std::string &message)
        {

            std::string filename = _logpath + _logname;
            // 如果不存在就创建，存在就追加写入, 默认对象构造完了就文件就打开了。
            LockGuard lock(_mutex);
            std::fstream out(filename, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n"; // 这样输入的数据就不需要添加空格了
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        std::string _logpath;
        std::string _logname;
        Mutex _mutex;
    };

    enum class LogLevel {
        DEBUG = 1, //调试信息
        INFO,      //正常显示
        WARNING,   //警告信息，不影响程序执行
        ERROR,     //错误信息
        FATAL      //致命错误，程序终止
    };

    std::string GetCurrtime(){

        char buffer[1024];
        time_t t =time(nullptr);
        struct tm curr;
        localtime_r(&t,&curr); 
        snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
        curr.tm_year + 1900,
        curr.tm_mon + 1,
        curr.tm_mday,
        curr.tm_hour,
        curr.tm_min,
        curr.tm_sec);

        return buffer;
    }

    std::string Level2string(const LogLevel& level){
        switch(level){
            case LogLevel::DEBUG:
                return "DEBUG";
            case LogLevel::INFO:
                return "INFO";
            case LogLevel::WARNING:
                return "WARNING";
            case LogLevel::ERROR:
                return "ERROR";
            case LogLevel::FATAL:
                return "FATAL";              
            default:
                return "NONE";                                                  
        }
    }
    // 接下来就是构建日志的内容了
    //Logger是来决定，日志究竟输出到哪
    class Logger
    {
    private:
        std::shared_ptr<Logstrategy> _strategy;
    public:
        Logger()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsole()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFilesystem()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        //LogMessage类中代表的就是一条日志信息，包括我们 << 用户自己输入的内容
        class LogMessage{
            private:
                std::string _currtime;  //当前的时间
                LogLevel _level;        //日志等级
                pid_t _pid;              //进程ID
                std::string _filename;  //文件名称
                int _line;              //代码的行号
                Logger& _logger;
                std::string _loginfo;   //这里保存的一条完整的日志信息
            public:
                LogMessage(const std::string& filename,const LogLevel& level,const int line,Logger& logger)
                    :_currtime(GetCurrtime()),
                    _level(level),
                    _filename(filename),
                    _line(line),
                    _pid(::getpid()),
                    _logger(logger)
                {
                    std::stringstream ss;
                    ss << "[" << _currtime << "] "
                       << "[" << Level2string(_level) << "] "
                       << "[" << _pid                 << "] "
                       << "[" << _filename            << "] "
                       << "[" << _line                << "] - ";
                    
                       _loginfo = ss.str();
                }
                template<class T>
                LogMessage& operator <<(const T& info){
                    std::stringstream ss;
                    ss << info;
                    _loginfo += ss.str();
                    return *this;
                }
                ~LogMessage()
                {
                    //如果智能指针有效
                    if (_logger._strategy){
                        _logger._strategy->SysChontrolLog(_loginfo);
                    }
                    //下面为什么我要返回一个临时对象，这样只在当前的代码剩下，然后析构直接输出
                    //虽然是临时对象但是 << << 在同一行，会执行完毕的。
                }
        };
        //就是要返回一个临时对象 ==》 LogMessage的析构
        LogMessage operator()(const std::string& filename,const LogLevel& level,const int line){
            return LogMessage(filename,level,line,*this);
        }

        ~Logger() {}
    };
    Logger logger;
    //c/c++ 默认自带的宏，代表当前代码的文件名和行号
    #define LOG(type) logger(__FILE__,type,__LINE__)
    #define CONTROLCONSOLESLOG() logger.EnableConsole()
    #define CONTROLFILESYSTEM() logger.EnableFilesystem()
}